/* Copyright(C) 2006-2008 Dave Sexton  *
 * http://www.codeplex.com/aip         *
 * http://www.codeplex.com/aip/license *
 ***************************************/
using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Drawing2D;
using System.ComponentModel;
using System.Collections.ObjectModel;
using DaveSexton.Web.Controls.Resources;

namespace DaveSexton.Web.Controls
{
	/// <summary>
	/// An abstract base class that generates an image for a given <see cref="AutoInputProtectionTextProvider"/> implementation and 
	/// a collection of <see cref="AutoInputProtectionFilterProvider"/> implementations.
	/// </summary>
	/// <remarks>
	/// <para>
	/// An implementation of <see cref="AutoInputProtectionImageProvider"/> is responsible for creating an instance of a type that
	/// implements AutoInputProtectionImage.
	/// </para>
	/// <para>
	/// One built-in implementation is an abstract base class named, <see cref="BackgroundAutoInputProtectionImage"/>, which provides
	/// an implementation for <see cref="CreateSurfaceImage"/> that renders a background image that is specified by a derived type.
	/// </para>
	/// <para>
	/// An implementation that derives from <see cref="BackgroundAutoInputProtectionImage"/> is <see cref="PartitionedAutoInputProtectionImage"/>, which 
	/// renders the individual characters of the text provided by the <see cref="AutoInputProtectionTextProvider"/> into vertical 
	/// partitions, with options for randomizing character rotation and for specifying fonts and the image's margins.
	/// </para>
	/// <para>
	/// One built-in implementation of <see cref="PartitionedAutoInputProtectionImage"/> is <see cref="LineNoiseAutoInputProtectionImage"/>, which 
	/// creates a surface bitmap that consists of a white background with random lines drawn in a variety of random colors.  The colors are specified by 
	/// the <see cref="LineNoiseAutoInputProtectionImageProvider"/> that creates it.
	/// </para>
	/// </remarks>
	/// <seealso cref="AutoInputProtectionImageProvider"/>
	public abstract class AutoInputProtectionImage : IDisposable
	{
		#region Public Properties
		/// <summary>
		/// The minimum dimensions for an image generated by this class.
		/// </summary>
		public static readonly Size MinimumSize = new Size(1, 1);

		/// <summary>
		/// Gets the composite <see cref="Image"/> that was gemeraed by the <see cref="CreateCompositeImage(Graphics,Size)"/> method.
		/// </summary>
		/// <value>The composite image that was generated by <see cref="CreateCompositeImage(Graphics,Size)"/>.</value>
		public Image CompositeImage
		{
			get
			{
				return compositeImage;
			}
		}

		/// <summary>
		/// Gets a list of <see cref="Font"/> objects that may be sampled by derived types when the <see cref="CompositeImage"/> is generated.
		/// </summary>
		/// <remarks>
		/// This property is initialized with the <see cref="AutoInputProtectionTextProvider.Fonts">Fonts</see> from the specified 
		/// <see cref="AutoInputProtectionTextProvider"/> when an instance of this class is created by the 
		/// <see cref="AutoInputProtectionImage(Size,AutoInputProtectionTextProvider)"/> constructor.
		/// </remarks>
		/// <value>A list of <see cref="Font"/> objects that are sampled when the <see cref="CompositeImage"/> is generated.</value>
		public IList<Font> Fonts
		{
			get
			{
				return fonts;
			}
		}

		/// <summary>
		/// Gets a list of <see cref="Color"/> objects that may be sampled by derived types when the <see cref="CompositeImage"/> is generated.
		/// </summary>
		/// <remarks>
		/// This property is initialized with the <see cref="AutoInputProtectionTextProvider.Colors">Colors</see> from the specified 
		/// <see cref="AutoInputProtectionTextProvider"/> when an instance of this class is created by the 
		/// <see cref="AutoInputProtectionImage(Size,AutoInputProtectionTextProvider)"/> constructor.
		/// </remarks>
		/// <value>A list of <see cref="Color"/> objects that are sampled when the <see cref="CompositeImage"/> is generated.</value>
		public IList<Color> TextColors
		{
			get
			{
				return textColors;
			}
		}

		/// <summary>
		/// Gets a read-only collection of <see cref="AutoInputProtectionFilterProvider"/> implementations that are applied when the 
		/// <see cref="CompositeImage"/> is generated.
		/// </summary>
		/// <value>A read-only collection of <see cref="AutoInputProtectionFilterProvider"/> implementations that are applied when the
		/// <see cref="CompositeImage"/> is generated.</value>
		public ReadOnlyCollection<AutoInputProtectionFilterProvider> Filters
		{
			get
			{
				List<AutoInputProtectionFilterProvider> list = new List<AutoInputProtectionFilterProvider>(filters.Count);

				foreach (AutoInputProtectionFilterProvider provider in filters)
					list.Add(provider);

				return list.AsReadOnly();
			}
		}

		/// <summary>
		/// Gets the string that is rendered in the <see cref="CompositeImage"/>.
		/// </summary>
		/// <remarks>
		/// <para>
		/// A string is only generated the first time that this property is read.  Subsequent reads will return the same string
		/// for the lifetime of the instance.
		/// </para>
		/// <para>
		/// To generate the string this property calls the 
		/// <see cref="AutoInputProtectionTextProvider.GenerateRandomAutoInputProtectionText"/> method of the 
		/// <see cref="AutoInputProtectionTextProvider"/> that was specified when an instance of this class was constructed.
		/// </para>
		/// </remarks>
		/// <exception cref="InvalidOperationException">A <see langword="null"/> or empty string was returned by the 
		/// <see cref="AutoInputProtectionTextProvider.GenerateRandomAutoInputProtectionText"/> method of the 
		/// <see cref="AutoInputProtectionTextProvider"/> that was specified when an instance of this class was constructed.
		/// </exception>
		/// <value>String that is rendered in the <see cref="CompositeImage"/>.</value>
		public string Text
		{
			get
			{
				if (text == null)
				{
					text = textProvider.GenerateRandomAutoInputProtectionText();

					if (string.IsNullOrEmpty(text))
						throw new InvalidOperationException(Errors.AIP_Text_NullOrEmpty);
				}

				return text;
			}
		}

		/// <summary>
		/// Gets the size of the <see cref="CompositeImage"/>.
		/// </summary>
		/// <value>Size of the composite image.</value>
		public Size Size
		{
			get
			{
				return size;
			}
		}
		#endregion

		#region Private / Protected
		private readonly AutoInputProtectionTextProvider textProvider;
		private string text;
		private readonly Size size;
		private Image compositeImage;
		private readonly List<Font> fonts;
		private readonly List<Color> textColors;

		internal AutoInputProtectionFilterProviderCollection filters;
		#endregion

		#region Constructors
		/// <summary>
		/// Constructs a new instance of the <see cref="AutoInputProtectionImage" /> class for derived classes.
		/// </summary>
		/// <exception cref="ArgumentNullException">The specified <paramref name="textProvider"/> is <see langword="null"/>.</exception>
		/// <exception cref="ArgumentOutOfRangeException">The specified <paramref name="size"/> is less than the value of 
		/// <see cref="MinimumSize"/>.</exception>
		/// <param name="size">The dimensions of the <see cref="CompositeImage"/>.</param>
		/// <param name="textProvider">The <see cref="AutoInputProtectionTextProvider"/> implementation that generates 
		/// the <see cref="Text"/> for the <see cref="CompositeImage"/>.</param>
		protected AutoInputProtectionImage(Size size, AutoInputProtectionTextProvider textProvider)
		{
			if (textProvider == null)
				throw new ArgumentNullException("textProvider");

			if (size.Width < MinimumSize.Width || size.Height < MinimumSize.Height)
				throw new ArgumentOutOfRangeException("size", size, Errors.SizePositiveIntRequired);

			this.textProvider = textProvider;
			this.size = size;
			this.fonts = new List<Font>(textProvider.Fonts);
			this.textColors = new List<Color>(textProvider.Colors);
		}
		#endregion

		#region Methods
		/// <summary>
		/// Applies all of the preprocess filters in the specified <paramref name="filters"/> collection to the specified <paramref name="graphics"/> object.
		/// </summary>
		/// <param name="filters">Collection of <see cref="AutoInputProtectionFilterProviderCollection"/> implementations to be applied.</param>
		/// <param name="graphics"><see cref="Graphics"/> object that provides the surface on which to draw.</param>
		/// <param name="imageSize">The dimensions of the image.</param>
		private static void PreProcessFilters(AutoInputProtectionFilterProviderCollection filters, Graphics graphics, Size imageSize)
		{
			foreach (AutoInputProtectionFilterProvider filter in filters)
			{
				if (filter.CanPreProcess)
					filter.PreProcess(graphics, imageSize);
			}
		}

		/// <summary>
		/// Applies all of the post-process filters in the specified <paramref name="filters"/> collection to the specified <paramref name="graphics"/> object.
		/// </summary>
		/// <param name="filters">Collection of <see cref="AutoInputProtectionFilterProviderCollection"/> implementations to be applied.</param>
		/// <param name="graphics"><see cref="Graphics"/> object that provides the surface on which to draw.</param>
		/// <param name="imageSize">The dimensions of the image.</param>
		private static void PostProcessFilters(AutoInputProtectionFilterProviderCollection filters, Graphics graphics, Size imageSize)
		{
			foreach (AutoInputProtectionFilterProvider filter in filters)
			{
				if (filter.CanPostProcess)
					filter.PostProcess(graphics, imageSize);
			}
		}

		/// <summary>
		/// When implemented by a derived type, returns an <see cref="Image"/> of the specified <paramref name="size"/> to be used as the surface on which 
		/// the <see cref="CompositeImage"/> will be generated.
		/// </summary>
		/// <remarks>
		/// <para>
		/// Derived implementations may return <see langword="null"/>, in which case <see cref="CreateCompositeImage()"/> will use a pure white surface.
		/// </para>
		/// <para>
		/// If a derived implementation returns an <see cref="Image"/> of a different <paramref name="size"/>, it will be scaled automatically.
		/// </para>
		/// </remarks>
		/// <param name="size">The dimensions of the image.</param>
		/// <returns>An <see cref="Image"/> of the specified <paramref name="size"/>.</returns>
		protected abstract Image CreateSurfaceImage(Size size);

		/// <summary>
		/// Creates the <see cref="CompositeImage"/> value.
		/// </summary>
		/// <remarks>
		/// <para>
		/// This method obtains a surface image from <see cref="CreateSurfaceImage"/>, applies all preprocess <see cref="Filters"/>, 
		/// calls the abstract <see cref="CreateCompositeImage(Graphics,Size)"/> method so that derived types can render the text on the image, 
		/// applies all post-process <see cref="Filters"/> and then returns the generated <see cref="Image"/> to the caller.
		/// </para>
		/// </remarks>
		/// <seealso cref="CompositeImage"/>
		/// <seealso cref="CreateCompositeImage(Graphics,Size)"/>
		/// <seealso cref="CreateSurfaceImage"/>
		/// <returns>The <see cref="Image"/> that is generated.</returns>
		internal Image CreateCompositeImage()
		{
			if (fonts.Count == 0)
				throw new InvalidOperationException(Resources.Errors.MissingFontSamplesForOverlay);

			if (textColors.Count == 0)
				throw new InvalidOperationException(Resources.Errors.MissingColorSamplesForOverlay);

			compositeImage = CreateSurfaceImage(size);
			bool isEmpty = false;

			if (compositeImage == null)
			{
				isEmpty = true;
				compositeImage = new Bitmap(size.Width, size.Height);
			}
			else if (compositeImage.Size != size)
				compositeImage = new Bitmap(compositeImage, size);

			using (Graphics graphics = Graphics.FromImage(compositeImage))
			{
				graphics.TextRenderingHint = System.Drawing.Text.TextRenderingHint.AntiAlias;
				graphics.SmoothingMode = SmoothingMode.AntiAlias;
				graphics.Clip = new Region(new Rectangle(0, 0, size.Width, size.Height));

				if (isEmpty)
					graphics.FillRectangle(Brushes.White, 0, 0, size.Width, size.Height);

				if (filters != null)
					PreProcessFilters(filters, graphics, size);

				CreateCompositeImage(graphics, size);

				if (filters != null)
					PostProcessFilters(filters, graphics, size);
			}

			return compositeImage;
		}

		/// <summary>
		/// When implemented by a derived type, renders the <see cref="Text"/> to the specified <paramref name="graphics"/> object.
		/// </summary>
		/// <remarks>
		/// This method is called by <see cref="CreateCompositeImage()"/> after all of the preprocess <see cref="Filters"/> have 
		/// been applied.
		/// </remarks>
		/// <seealso cref="CompositeImage"/>
		/// <seealso cref="CreateCompositeImage()"/>
		/// <param name="graphics"><see cref="Graphics"/> object that provides the surface on which to draw.</param>
		/// <param name="size">The dimensions of the image.</param>
		protected abstract void CreateCompositeImage(Graphics graphics, Size size);
		#endregion

		#region IDisposable Members
		/// <summary>
		/// Releases all resources used by an instance of the <see cref="AutoInputProtectionImage" /> class.
		/// </summary>
		/// <remarks>
		/// This method calls the virtual <see cref="Dispose(bool)" /> method, passing in <strong>true</strong>, and then suppresses 
		/// finalization of the instance.
		/// </remarks>
		public void Dispose()
		{
			Dispose(true);
			GC.SuppressFinalize(this);
		}

		/// <summary>
		/// Releases unmanaged resources before an instance of the <see cref="AutoInputProtectionImage" /> class is reclaimed by garbage collection.
		/// </summary>
		/// <remarks>
		/// This method releases unmanaged resources by calling the virtual <see cref="Dispose(bool)" /> method, passing in <strong>false</strong>.
		/// </remarks>
		~AutoInputProtectionImage()
		{
			Dispose(false);
		}

		/// <summary>
		/// Releases the unmanaged resources used by an instance of the <see cref="AutoInputProtectionImage" /> class and optionally releases the managed resources.
		/// </summary>
		/// <param name="disposing"><strong>true</strong> to release both managed and unmanaged resources; <strong>false</strong> to release only unmanaged resources.</param>
		protected virtual void Dispose(bool disposing)
		{
			if (disposing && compositeImage != null)
				compositeImage.Dispose();
		}
		#endregion
	}
}
